/** {@code Rule}类 实用工具类 */
public class Rule {
    /** 七选五全组合结果 */
    private static final int[][] combination = { { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 5 }, { 0, 1, 2, 3, 6 },
            { 0, 1, 2, 4, 5 }, { 0, 1, 2, 4, 6 }, { 0, 1, 2, 5, 6 }, { 0, 1, 3, 4, 5 }, { 0, 1, 3, 4, 6 },
            { 0, 1, 3, 5, 6 }, { 0, 1, 4, 5, 6 }, { 0, 2, 3, 4, 5 }, { 0, 2, 3, 4, 6 }, { 0, 2, 3, 5, 6 },
            { 0, 2, 4, 5, 6 }, { 0, 3, 4, 5, 6 }, { 1, 2, 3, 4, 5 }, { 1, 2, 3, 4, 6 }, { 1, 2, 3, 5, 6 },
            { 1, 2, 4, 5, 6 }, { 1, 3, 4, 5, 6 }, { 2, 3, 4, 5, 6 } };
    /** 由小到大排序后的七张p牌(起脚牌+公共牌) */
    private static Card[] sortedCard = new Card[7];
    /** 被选择的五张牌 */
    private static Card[] selectedCard = new Card[5];

    /** 纯方法类不可以构造 */
    private Rule() {
        throw new IllegalStateException("Utility class");
    }

    /** 获取玩家 p 的牌力评价分 */
    public static long getScore(Player player) {
        /* 牌型大小: 同花顺=9 四条=8 葫芦=7 同花=6 顺子=5 三条=4 两对=3 对子=2 散牌=1 */
        long score = 0;// 牌力评分
        /* 处理起脚牌+公共牌并排序 */
        pretreat(player);
        /* 七抽五 计算牌型牌力 */
        for (int i = 0; i < 21; i++) {// C^5_7=21
            // 从排序后的牌中抽五张
            for (int j = 0; j < 5; j++) {
                int choiceNo = Rule.combination[i][j];
                selectedCard[j] = sortedCard[choiceNo];
            }
            score = Math.max(score, scoring(player));// 取最大的牌力评分
        }
        // <Debug>
        String[] t = new String[] { "散牌", "对子", "两对", "三条", "顺子", "同花", "葫芦", "四条", "同花顺" };
        System.out.println(player.toString() + '\t' + sortedCard[0] + '\t' + sortedCard[1] + '\t' + sortedCard[2] + '\t'
                + sortedCard[3] + '\t' + sortedCard[4] + '\t' + sortedCard[5] + '\t' + sortedCard[6] + '\t'
                + t[(int) (score >> 56) - 1] + '\t' + Long.toHexString(score));
        return score;
    }

    /** 处理起脚牌+公共牌并排序 */
    private static void pretreat(Player player) {
        // 获取两张起脚牌
        sortedCard[0] = player.getPrivateCard()[0];
        sortedCard[1] = player.getPrivateCard()[1];
        // 获取五张公共牌
        for (int i = 0; i < 5; i++)
            sortedCard[i + 2] = Game.publicCard[i];
        // 冒泡排序
        for (int i = 0; i < 6; i++)
            for (int j = 0; j < 6 - i; j++)
                if (sortedCard[j].getValue() > sortedCard[j + 1].getValue()) {
                    Card temp = sortedCard[j];
                    sortedCard[j] = sortedCard[j + 1];
                    sortedCard[j + 1] = temp;
                }
    }

    /** 计算并返回牌力 */
    private static long scoring(Player player) {
        long score = 0;// 牌力评分
        if (is同花顺()) {
            long type = 9;// 牌型
            long face = selectedCard[4].getFace();// 高牌点数
            long suit = selectedCard[4].getSuit();// 高牌花色
            score = (type << 56) + (face << 52) + (suit << 48);
        } else if (is四条()) {
            long type = 8;// 牌型
            long face = selectedCard[2].getFace();// 高牌点数 最中间的牌一定是组成四条的一部分
            score = (type << 56) + (face << 52);
        } else if (is葫芦()) {
            long type = 7;// 牌型
            long face = selectedCard[2].getFace();// 高牌点数 中间的牌一定是三条
            long face2 = (selectedCard[0].getFace() != face) ? selectedCard[0].getFace() : selectedCard[4].getFace();
            score = (type << 56) + (face << 52) + (face2 << 48);
        } else if (is同花()) {
            long type = 6;
            long face = selectedCard[4].getFace();// 第一大牌点数
            long face2 = selectedCard[3].getFace();// 第二大牌点数
            long face3 = selectedCard[2].getFace();// 第三大牌点数
            long face4 = selectedCard[1].getFace();// 第四大牌点数
            long face5 = selectedCard[0].getFace();// 第五大牌点数
            long suit = selectedCard[0].getSuit();// 花色
            score = (type << 56) + (face << 52) + (face2 << 48) + (face3 << 44) + (face4 << 40) + (face5 << 36)
                    + (suit << 32);
        } else if (is顺子()) {
            long type = 5;
            long face = selectedCard[4].getFace();// 高牌点数
            long suit = selectedCard[4].getSuit();// 高牌花色
            long suit2 = selectedCard[3].getSuit();// 2高牌花色
            long suit3 = selectedCard[2].getSuit();// 3高牌花色
            long suit4 = selectedCard[1].getSuit();// 4高牌花色
            long suit5 = selectedCard[0].getSuit();// 5高牌花色
            score = (type << 56) + (face << 52) + (suit << 48) + (suit2 << 44) + (suit3 << 40) + (suit4 << 36)
                    + (suit5 << 32);
        } else if (is三条()) {
            long type = 4;
            long face = selectedCard[2].getFace();// 高牌点数 中间的牌一定是三条
            score = (type << 56) + (face << 52);
        } else if (is两对()) {
            score = get两对();
        } else if (is对子()) {
            score = get对子();
        } else {// is散牌
            long type = 1;
            score = (type << 56);
        }
        // 起脚牌
        int maxCard = (player.getPrivateCard()[0].getValue() > player.getPrivateCard()[1].getValue()) ? 0 : 1;
        score += player.getPrivateCard()[maxCard].getFace() << 8;// 最大牌点数
        score += player.getPrivateCard()[1 - maxCard].getFace() << 4;// 第二牌点数
        score += player.getPrivateCard()[maxCard].getSuit();// 最大牌花色
        return score;
    }

    private static boolean is同花顺() {
        // 是同花且是顺子
        return is同花() && is顺子();
    }

    private static boolean is同花() {
        // 五张牌花色相同
        return suitEqual(0, 1) && suitEqual(1, 2) && suitEqual(2, 3) && suitEqual(3, 4);
    }

    private static boolean is顺子() {
        // 五张牌为d=1的等差数列, 例: 12345, 45678
        return (selectedCard[4].getFace() == selectedCard[3].getFace() + 1)
                && (selectedCard[3].getFace() == selectedCard[2].getFace() + 1)
                && (selectedCard[2].getFace() == selectedCard[1].getFace() + 1)
                && (selectedCard[1].getFace() == selectedCard[0].getFace() + 1);
    }

    private static boolean is四条() {
        // 1234或2345号牌相等<==>14或25相等(已排序), 例: 11114, 35555
        return faceEqual(0, 3) || faceEqual(1, 4);
    }

    private static boolean is葫芦() {
        // (123相等45相等)或(12相等345相等), 例: 33355, 77999
        return faceEqual(0, 2) && faceEqual(3, 4) || faceEqual(0, 1) && faceEqual(2, 4);
    }

    private static boolean is三条() {
        // (123相等)或(234相等)或(345相等), 例: 22245, 46667, 25999
        return faceEqual(0, 2) || faceEqual(1, 3) || faceEqual(2, 4);
    }

    private static boolean is两对() {
        // 12,34,5或12,3,45或1,23,45分别相等
        return faceEqual(0, 1) && faceEqual(2, 3) || faceEqual(0, 1) && faceEqual(3, 4)
                || faceEqual(1, 2) && faceEqual(3, 4);
    }

    private static boolean is对子() {
        // 12相等或23相等或34相等或45相等
        return faceEqual(0, 1) || faceEqual(1, 2) || faceEqual(2, 3) || faceEqual(3, 4);
    }

    /** 牌 a 与牌 b 花色相同 */
    private static boolean suitEqual(int a, int b) {
        return selectedCard[a].getSuit() == selectedCard[b].getSuit();
    }

    /** 牌 a 与牌 b 点数相同 */
    private static boolean faceEqual(int a, int b) {
        return selectedCard[a].getFace() == selectedCard[b].getFace();
    }

    private static long get两对() {
        long type = 3;
        long bigFace = selectedCard[3].getFace();// 大对点数 第四张的牌
        long smallFace = selectedCard[1].getFace();// 小对点数 第二张的牌
        long singleFace = 0;// 一三五都有可能
        if (selectedCard[0].getFace() != smallFace)
            singleFace = selectedCard[0].getFace();// 第一张为单牌
        else if (selectedCard[4].getFace() != bigFace)
            singleFace = selectedCard[4].getFace();// 第五张为单牌
        else
            singleFace = selectedCard[2].getFace();// 第三张为单牌
        return (type << 56) + (bigFace << 52) + (smallFace << 48) + (singleFace << 44);
    }

    private static long get对子() {
        long type = 2;
        int faceIndex = 0;
        while (selectedCard[faceIndex].getFace() != selectedCard[faceIndex + 1].getFace())
            faceIndex++;
        long face = selectedCard[faceIndex].getFace();// 对子点数
        long[] singleFace = new long[3];
        int n = 0;
        for (int i = 0; i < 5; i++) {
            if (i != faceIndex && i != faceIndex + 1) {
                singleFace[n++] = selectedCard[i].getFace();
            }
        }
        return (type << 56) + (face << 52) + (singleFace[2] << 48) + (singleFace[1] << 44) + (singleFace[0] << 40);
    }
}
